<template>
  <div class="container">
    <!-- 单一文件上传 [ FormData ] -->
    <div class="upload-case">
      <h3 class="upload-name">单一文件上传 [ FormData ]</h3>
      <div class="upload-content">
        <div class="flex align-center">
          <div class="upload-btn-case">
            <input type="file" class="upload-inp" ref="upload1" @change="input1Change">
            <el-button type="primary" class="upload-btn">选择图片</el-button>
          </div>
          <el-button type="success" class="upload-server" @click="upload1ToServer">上传到服务器</el-button>
        </div>
        <div class="file-case">
          <div class="file-name" v-if="showFileName1">
            名字：{{fileName1}} <el-link type="danger" @click="upload1Remove">移除</el-link>
          </div>
        </div>
      </div>
    </div>
    <!-- 单一文件上传 [ Base64 ] 只适合图片 -->
    <div class="upload-case">
      <h3 class="upload-name">单一文件上传 [ Base64 ] 只适合图片</h3>
      <div class="upload-content">
        <div class="upload-btn-case">
          <input type="file" class="upload-inp" ref="upload2" @change="input2Change">
          <el-button type="primary" class="upload-btn">选择图片</el-button>
        </div>
      </div>

    </div>
    <!-- 单一文件上传 [ 缩略图处理 ] -->
    <div class="upload-case">
      <h3 class="upload-name">单一文件上传 [ 缩略图处理 ]</h3>
      <div class="upload-content">
        <div class="flex align-center">
          <div class="upload-btn-case">
            <input type="file" class="upload-inp" ref="upload3" @change="input3Change">
            <el-button type="primary" class="upload-btn">选择图片</el-button>
          </div>
          <el-button type="success" class="upload-server" @click="upload3ToServer">上传到服务器</el-button>
        </div>
        <div class="flex align-center" style="gap:10px;margin-top:10px" v-if="upload3Src!==''">
          <img @click="upload3Src=''" class="upload-src" :src="upload3Src" alt="" srcset="">
        </div>
      </div>
    </div>
    <!-- 单一文件上传 [ 自己处理 HASH 文件名 ] -->
    <div class="upload-case">
      <h3 class="upload-name">单一文件上传 [ 自己处理 HASH 文件名 ]</h3>
      <div class="upload-content">
        <div class="flex align-center">
          <div class="upload-btn-case">
            <input type="file" class="upload-inp" ref="upload4" @change="input4Change">
            <el-button type="primary" class="upload-btn">选择文件</el-button>
          </div>
        </div>
        <div class="flex align-center" style="gap:10px;margin-top:10px" v-if="upload4Src!==''">
          <img @click="upload4Src=''" class="upload-src" :src="upload4Src" alt="" srcset="">
        </div>
      </div>
    </div>
    <!-- 单一文件上传 [ 进度管控 ] -->
    <div class="upload-case">
      <h3 class="upload-name">单一文件上传 [ 进度管控 ]</h3>
      <div class="upload-content">
        <div class="upload-btn-case">
          <input type="file" class="upload-inp" ref="upload5" @change="input5Change">
          <el-button type="primary" class="upload-btn">选择文件</el-button>
        </div>
        <div class="progress flex align-center justify-center" style="margin-top:20px">
          <el-progress type="circle" :percentage="upload5Progress"></el-progress>
        </div>
      </div>
    </div>
    <!-- 多文件上传 -->
    <div class="upload-case">
      <h3 class="upload-name">多文件上传 [ 进度管控 ]</h3>
      <div class="upload-content">
        <div class="flex align-center">
          <div class="upload-btn-case">
            <input type="file" class="upload-inp" multiple ref="upload6" @change="input6Change">
            <el-button type="primary" class="upload-btn">选择文件</el-button>
          </div>
          <el-button type="success" class="upload-server" @click="upload6ToServer">上传到服务器</el-button>
        </div>
        <div class="flex align-center" style="gap:10px;margin-top:10px" v-if="upload6Files.length>0">
          <div class="upload-src-box" v-for="(item,index) in upload6Files" :key="item.id">
            <img class="upload-src" @click="upload6Remove(index)" :src="item.base64" alt="" srcset="">
            <el-progress v-if="upload6IsUploading" class="upload-progress" :show-text="false" :stroke-width="10" :percentage="item.progress"></el-progress>
          </div>
        </div>
      </div>
    </div>
    <!-- 拖拽上传 -->
    <div class="upload-case">
      <h3 class="upload-name">拖拽上传</h3>
      <div class="upload-content">
        <div class="upload-drag-case" @dragover.prevent @drop.prevent="upload7Drag">
          <p>拖拽上传或点击上传</p>
          <input type="file" @change="input7Change" class="upload-inp" multiple ref="upload7">
        </div>
      </div>
    </div>
    <!-- 大文件上传 -->
    <div class="upload-case">
      <h3 class="upload-name">大文件上传</h3>
      <div class="upload-content">
        <div class="upload-btn-case">
          <input type="file" class="upload-inp" ref="upload8" @change="input8Change">
          <el-button type="primary" class="upload-btn" :loading="upload8IsUploading">选择文件</el-button>
        </div>
        <div class="progress flex align-center justify-center" style="margin-top:20px">
          <el-progress type="circle" :percentage="upload8Progress"></el-progress>
        </div>
      </div>
    </div>
  </div>
</template>
<script>
import uploadApi from '@/api/upload.js'
import SparkMD5 from 'spark-md5'
import { v4 } from 'uuid'
export default {
  name: 'Upload',
  components: {},
  data() {
    return {
      uploadDom: {
        one: null,
        two: null,
        three: null,
        four: null,
        five: null,
        six: null,
        seven: null,
        eight: null
      },
      // upload 1
      showFileName1: false,
      fileName1: '',
      // upload 3
      upload3Src: '',
      upload3Param: null,
      // upload 4
      upload4Src: '',
      // upload 5
      upload5Progress: 0,
      // upload 6
      upload6Files: [],
      upload6IsUploading: false,
      // upload 8
      uploadHash: '',
      upload8Already: [],
      upload8ChunkIndex: 0,
      upload8Max: null,
      upload8Count: null,
      upload8Progress: 0,
      upload8IsUploading: false,
    }
  },
  mounted() {
    // 获取所有的 input 元素
    this.uploadDom.one = this.$refs.upload1
    this.uploadDom.two = this.$refs.upload2
    this.uploadDom.three = this.$refs.upload3
    this.uploadDom.four = this.$refs.upload4
    this.uploadDom.five = this.$refs.upload5
    this.uploadDom.six = this.$refs.upload6
    this.uploadDom.seven = this.$refs.upload7
    this.uploadDom.eight = this.$refs.upload8
  },
  methods: {
    /**
     * @description: Buffer
     */
    changeBuffer(file) {
      return new Promise((resolve) => {
        const fileReader = new FileReader()
        fileReader.readAsArrayBuffer(file)
        fileReader.onload = ev => {
          let buffer = ev.target.result,
            md5 = new SparkMD5.ArrayBuffer(),
            hash,
            suffix;
          md5.append(buffer)
          hash = md5.end()
          suffix = /\.([a-zA-Z0-9]+)$/.exec(file.name)[1];
          resolve({
            buffer,
            hash,
            suffix,
            filename: `${hash}.${suffix}`
          })
        }
      })
    },
    /**
     * @description: 把文件读取成 base64 格式
     */
    changeBase64(file) {
      return new Promise((resolve) => {
        const fileReader = new FileReader()
        // readAsDataURL方法会使用base-64进行编码，编码的资料由data字串开始，后面跟随的是MIME type，然后再加上base64字串，逗号之后就是编码过的图像文件的内容。
        fileReader.readAsDataURL(file)
        fileReader.onload = ev => {
          const base64 = ev.target.result
          resolve({
            base64,
            // 可把字符串作为URI 组件进行编码。其返回值URIstring 的副本，其中的某些字符将被十六进制的转义序列进行替换。
            file: encodeURIComponent(base64),
            filename: file.name
          })
        }
      })
    },
    /**
     * @description: upload1 change 事件
     */
    input1Change() {
      const file = this.uploadDom.one.files[0]
      if (!file) return
      /**
       * 限制文件的上传格式 [ 方案一: 使用 JS 正则判断 ]
       * input 元素上设置 accept=".png,.jpg,.jpeg" 可以限制用户仅能选择哪些文件格式，多个时用逗号隔开。 [ 方案二: 利用 input 属性 ]
       */
      const inspect = /(png|jpg|jpeg)/i
      if (!inspect.test(file.type)) {
        this.$message.error('文件只能上传 png、jpg、jpeg 格式的文件。');
        return
      }
      // 限制文件的上传大小
      const size = 2 * 1024 * 1024
      if (file.size > size) {
        this.$message.error('上传的文件不能超过2MB。');
        return
      }
      this.fileName1 = file.name
      this.showFileName1 = true
    },
    /**
     * @description: 移除 upload1 的文件
     */
    upload1Remove() {
      this.showFileName1 = false
      this.fileName1 = ''
      this.uploadDom.one.value = '' // 清空 files 列表
    },
    /**
     * @description:  upload1 上传到服务器
     */
    upload1ToServer() {
      const one = this.uploadDom.one
      const file = one.files[0]
      if (!file) {
        this.$message.error('请先上传文件。');
        return
      }
      // 把文件传递给服务器 FormData
      // 请求主体传递给服务器的数据格式: FromData(主要用于上传文件)/x-www-form-urlencoded(主要用于非文件)/json字符串/普通字符串/Buffer...
      // formData 的参数通过 fd.append 添加
      const fd = new FormData()
      fd.append('file', file)
      fd.append('filename', file.name)
      uploadApi.uploadSingle(fd).then(res => {
        this.$message({
          message: res.codeText,
          type: 'success'
        });
        this.upload1Remove()
      })
    },
    /**
    * @description: upload2 change 事件
    */
    async input2Change() {
      const file = this.uploadDom.two.files[0]
      if (!file) return
      const inspect = /(png|jpg|jpeg)/i
      if (!inspect.test(file.type)) {
        this.$message.error('文件只能上传 png、jpg、jpeg 格式的文件。');
        return
      }
      const size = 2 * 1024 * 1024
      if (file.size > size) {
        this.$message.error('上传的文件不能超过2MB。');
        return
      }
      const val = await this.changeBase64(file)
      uploadApi.uploadSingleBase64(val).then(res => {
        this.uploadDom.two.value = ''
        this.$message({
          message: res.codeText,
          type: 'success'
        });
      })
    },
    /**
    * @description: upload3 change 事件
    */
    async input3Change() {
      const file = this.uploadDom.three.files[0]
      if (!file) return
      const inspect = /(png|jpg|jpeg)/i
      if (!inspect.test(file.type)) {
        this.$message.error('文件只能上传 png、jpg、jpeg 格式的文件。');
        return
      }
      const size = 2 * 1024 * 1024
      if (file.size > size) {
        this.$message.error('上传的文件不能超过2MB。');
        return
      }

      const val = await this.changeBase64(file)
      this.upload3Param = val
      this.upload3Src = val.base64
    },
    /**
    * @description:  upload3 上传到服务器
    */
    upload3ToServer() {
      uploadApi.uploadSingleBase64(this.upload3Param).then(res => {
        this.uploadDom.three.value = ''
        this.$message({
          message: res.codeText,
          type: 'success'
        });
      })
    },
    /**
    * @description: upload4 change 事件
    * 单文件上传处理「FORM-DATA」:由客户端生成文件的名字，传递给服务器处理，这种情况出现于，后端不处理文件名，而同名的不同文件无法上传的情况，我们需要使用 spark-MD5 处理唯一的文件名。
    */
    async input4Change() {
      const file = this.uploadDom.four.files[0]
      if (!file) return
      const { filename } = await this.changeBuffer(file)
      const fd = new FormData()
      fd.append('file', file)
      fd.append('filename', filename)
      uploadApi.uploadSingleName(fd).then(res => {
        this.$message({
          message: res.codeText,
          type: 'success'
        });
        this.uploadDom.four.value = ''
      })
    },
    /**
     * @description: upload5 change 事件
     */
    async input5Change() {
      const file = this.uploadDom.five.files[0]
      if (!file) return
      const fd = new FormData()
      fd.append('file', file)
      fd.append('filename', file.name)
      uploadApi.uploadSingle(fd, (e) => {
        this.upload5Progress = parseInt(e.loaded / e.total * 100)
      }).then(res => {

        this.$message({
          message: res.codeText,
          type: 'success'
        });
        setTimeout(() => {
          this.upload5Progress = 0
        }, 1000)
        this.uploadDom.five.value = ''
      })
    },
    /**
     * @description: upload6 change 事件
     */
    input6Change() {
      const files = this.uploadDom.six.files
      if (!files) return
      Array.from(files).map((i) => {
        this.changeBase64(i).then((v) => {
          this.upload6Files.push({
            id: v4(),
            file: i,
            filename: v.filename,
            base64: v.base64,
            progress: 0
          })
        })
      })
      this.uploadDom.six.value = ''
    },
    /**
    * @description: 移除 upload6 的文件
    */
    upload6Remove(index) {
      this.upload6Files.splice(index, 1)
    },
    /**
    * @description:  upload6 上传到服务器
    */
    upload6ToServer() {
      if (this.upload6Files == 0) return
      this.upload6IsUploading = true
      const all = this.upload6Files.map((i) => {
        const fd = new FormData()
        fd.append('file', i.file)
        fd.append('filename', i.filename)
        return uploadApi.uploadSingle(fd, (e) => {
          i.progress = parseInt(e.loaded / e.total * 100)
        })
      })
      Promise.all(all).then(() => {
        this.$message({
          message: '上传成功',
          type: 'success'
        });
        this.upload6Files = []
        this.upload6IsUploading = false
      })
    },
    /**
     * @description: upload7 change 事件
     */
    input7Change() {
      const files = this.uploadDom.seven.files
      if (!files) return
      const all = Array.from(files).map((i) => {
        const fd = new FormData()
        fd.append('file', i)
        fd.append('filename', i.filename)
        return uploadApi.uploadSingle(fd)
      })
      Promise.all(all).then(() => {
        this.$message({
          message: '上传成功',
          type: 'success'
        });
        this.uploadDom.seven.value = ''
      })
    },
    /**
     * @description: upload7 拖拽上传
     */
    upload7Drag(event) {
      const files = event.dataTransfer.files
      if (!files) return
      const all = Array.from(files).map((i) => {
        const fd = new FormData()
        fd.append('file', i)
        fd.append('filename', i.filename)
        return uploadApi.uploadSingle(fd)
      })
      Promise.all(all).then(() => {
        this.$message({
          message: '上传成功',
          type: 'success'
        });
        this.uploadDom.seven.value = ''
      })
    },
    /**
     * @description: upload8 change 事件
     */
    async input8Change() {
      const file = this.uploadDom.eight.files[0]
      if (!file) return
      this.upload8IsUploading = true
      // 获取文件的 HASH
      let chunks = [],
        { hash, suffix } = await this.changeBuffer(file)
      this.uploadHash = hash
      // 获取已经上传的切片信息
      await uploadApi.uploadAlready(hash).then(res => {
        this.upload8Already = res.fileList
      })
      // 实现文件切片处理，实现文件的切片一半遵循如下两种切片的方式 [ 固定数量 & 固定大小 ]
      this.upload8Max = 1024 * 1024 * 1 // 固定每个切片为 1MB 大小
      this.upload8Count = Math.ceil(file.size / this.upload8Max) // 文件的大小 / 每个切片的大小 = 多少个切片
      if (this.upload8Count > 100) { // 如果切片的数量大于 100
        this.upload8Max = file.size / 100  // 文件的大小平分成 100 份
        this.upload8Count = 100 // 固定切成一百个切片
      }
      // 根据切片的数量开始切片
      for (let i = 0; i < this.upload8Count; i++) {
        chunks.push({
          file: file.slice(i * this.upload8Max, (i + 1) * this.upload8Max),
          filename: `${hash}_${i + 1}.${suffix}`
        })
      }
      // 把每个切片上传到服务器
      chunks.forEach((chunk) => {
        // 判断当前切片是否已经上传过，是则调过当前切片的上传
        if (this.upload8Already.length > 0 && this.upload8Already.includes(chunk.filename)) {
          this.chunkComplete()
          return
        }
        const fd = new FormData()
        fd.append('file', chunk.file)
        fd.append('filename', chunk.filename)
        uploadApi.uploadChunk(fd).then((res) => {
          this.chunkComplete()
        })
      })
    },
    /**
     * @description: 切片上传成功
     */
    chunkComplete() {
      this.upload8ChunkIndex++
      this.upload8Progress = parseInt(this.upload8ChunkIndex / this.upload8Count * 100)
      if (this.upload8ChunkIndex < this.upload8Count) return
      const param = {
        HASH: this.uploadHash,
        count: this.upload8Count
      }
      uploadApi.uploadMerge(param).then(res => {
        this.$message({
          message: '上传成功',
          type: 'success'
        });
        this.upload8Progress = 0
        this.uploadDom.eight.value = ''
        this.upload8IsUploading = false
      })
    }
  }

}
</script>
<style lang="scss" scoped>
.container {
  box-sizing: border-box;
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  grid-auto-rows: repeat(3, 1fr);
  gap: 20px;
  .upload-case {
    display: flex;
    flex-direction: column;
    row-gap: 10px;
    height: 250px;
    .upload-content {
      flex-grow: 1;
      box-sizing: border-box;
      padding: 10px;
      width: 100%;
      border: 1px solid #a6d3ff;
      border-radius: 10px;
      .upload-btn-case {
        position: relative;
        .upload-inp {
          cursor: pointer;
          opacity: 0;
          width: 100%;
          height: 100%;
          position: absolute;
          top: 0;
          left: 0;
        }
        .upload-btn {
          pointer-events: none;
          height: 40px;
        }
      }
      .upload-server {
        margin-left: 10px;
        height: 40px;
      }
      .upload-drag-case {
        position: relative;
        display: flex;
        align-items: center;
        justify-content: center;
        width: 100%;
        height: 100%;
        background-color: #ececec;
        .upload-inp {
          position: absolute;
          z-index: 9;
          width: 100%;
          height: 100%;
          opacity: 0;
        }
        p {
          z-index: 1;
          position: absolute;
        }
      }
      .file-case {
        .file-name {
          display: flex;
          align-items: center;
          gap: 0 10px;
          padding: 5px 0;
          font-size: 15px;
          color: #666;
        }
      }
      .upload-src-box {
        position: relative;
      }
      .upload-src-box,
      .upload-src {
        width: 100px;
        height: 100px;
        border-radius: 5px;
      }
      .upload-progress {
        z-index: 1;
        position: absolute;
        bottom: 0;
        width: 100%;
      }
    }
  }
}
</style>
